use std::{
    collections::HashMap,
    fs::DirEntry,
    path::{Path, PathBuf},
};

use mlua::prelude::*;

#[derive(Debug)]
pub enum FileType {
    File(String),
    LinkFile(String),
    Folder(String, Vec<FileType>),
    LinkFolder(String, Vec<FileType>),
}

#[derive(Debug)]
pub struct FileTree {
    pub root: FileType,
}

impl FileTree {
    pub fn display(self) {
        println!("{:#?}", self.root);
    }
}

#[derive(Debug, Eq, Hash, PartialEq)]
pub struct PathPrefix {
    name: String,
    level: usize,
}

impl PathPrefix {
    fn new(name: String, level: usize) -> Self {
        PathPrefix { name, level }
    }
}

#[allow(dead_code)]
#[derive(Debug)]
pub struct PathInfo {
    file_type: usize,
    file_list: Vec<(String, usize)>,
}

impl PathInfo {
    fn new(file_type: usize, file_list: Vec<(String, usize)>) -> Self {
        PathInfo {
            file_type,
            file_list,
        }
    }
}

pub fn tree_ls(path_dir: String) -> HashMap<PathPrefix, PathInfo> {
    let mut file_map = HashMap::new();
    let path = Path::new(&path_dir);
    if path.is_dir() {
        if let Ok(read_dir) = path.read_dir() {
            let mut buffer = Vec::new();
            let mut root_list = Vec::new();
            for dir_entry_result in read_dir {
                if let Ok(dir_entry) = dir_entry_result {
                    let path = dir_entry.path().display().to_string();
                    let name = path.split('/').last().unwrap().to_string();
                    root_list.push((name, get_file_type(dir_entry.path())));
                    buffer.push((dir_entry, 1));
                }
            }

            // root path
            file_map.insert(
                PathPrefix::new(path_dir.clone(), 0),
                PathInfo::new(get_file_type(path.to_path_buf()), root_list),
            );

            while !buffer.is_empty() {
                let (dir_entry, level) = &buffer[0];
                let p = dir_entry.path();
                let path = p.display().to_string();
                let name = path.split('/').last().unwrap().to_string();
                let level = *level;
                if p.is_symlink() {
                    if p.is_file() {
                        file_map.insert(PathPrefix::new(name, level), PathInfo::new(2, vec![]));
                    } else if p.is_dir() {
                        file_map.insert(PathPrefix::new(name, level), PathInfo::new(3, vec![]));
                    }
                } else if p.is_file() {
                    file_map.insert(PathPrefix::new(name, level), PathInfo::new(0, vec![]));
                } else if p.is_dir() {
                    let mut list = Vec::new();
                    if let Ok(child) = p.read_dir() {
                        for child_entry_result in child {
                            if let Ok(child_entry) = child_entry_result {
                                let path = child_entry.path().display().to_string();
                                let name = path.split('/').last().unwrap().to_string();
                                let file_type = get_file_type(child_entry.path());
                                list.push((name, file_type));
                                buffer.push((child_entry, level + 1));
                            }
                        }
                    }
                    file_map.insert(PathPrefix::new(name, level), PathInfo::new(1, list));
                }
                buffer.remove(0);
            }
        }
    }
    file_map
}

pub fn tree(lua: &Lua, path_dir: String) -> LuaResult<LuaTable> {
    let table = lua.create_table()?;
    let path = Path::new(&path_dir);
    if path.is_dir() {
        if let Ok(read_dir) = path.read_dir() {
            let mut buffer: Vec<DirEntry> = read_dir.into_iter().filter_map(|x| x.ok()).collect();
            while !buffer.is_empty() {
                let dir_entry = &buffer[0];
                let p = dir_entry.path();
                if p.is_symlink() {
                    if p.is_file() {
                        table.set(p.to_str(), "link file")?;
                    } else if p.is_dir() {
                        table.set(p.to_str(), "link folder")?;
                    }
                } else if p.is_file() {
                    table.set(p.to_str(), "file")?;
                } else if p.is_dir() {
                    table.set(p.to_str(), "dir")?;
                    if let Ok(child) = p.read_dir() {
                        for child_entry in child {
                            if child_entry.is_ok() {
                                buffer.insert(buffer.len(), child_entry.unwrap());
                            }
                        }
                    }
                }
                buffer.remove(0);
            }
        }
    }
    Ok(table)
}

/// get file type
/// 0: file
/// 1: folder
/// 2: link file
/// 3: link folder
fn get_file_type(path: PathBuf) -> usize {
    if path.is_symlink() {
        if path.is_file() {
            2
        } else if path.is_dir() {
            3
        } else {
            unreachable!()
        }
    } else if path.is_file() {
        0
    } else if path.is_dir() {
        1
    } else {
        unreachable!()
    }
}
